## App

We define the `<head>` tags (e.g. `<title>` or `<meta name="description">`)
of our app in our [`render()` hook](/render).

```js
// _default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'

export { render }

async function render(pageContext) {
  return escapeInject`<html>
    <head>
      <title>SpaceX</title>
      <meta name="description" content="We deliver payload to space.">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`
}
```


## Page — static

To define `<head>` tags (e.g. `<title>` or `<meta name="description">`)
for a specific page,
we can use [`pageContext.pageExports`](/pageContext).

```js
// about.page.js

// We statically export/determine `<head>` tags
export const documentProps = {
  // This title and description will override the defaults
  title: 'About SpaceX',
  description: 'Our mission is to explore the galaxy.'
}
```
```js
// _default.page.server.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'

export { render }

async function render(pageContext) {
  // We use `pageContext.pageExports.documentProps` which pages can statically define.
  const documentProps = pageContext.pageExports.documentProps

  // Defaults
  const title = documentProps.title || 'SpaceX'
  const description = documentProps.description || 'We deliver payload to space.'

  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`
}
```


## Page — dynamic

To define `<head>` tags (e.g. `<title>` or `<meta name="description">`)
that are dynamic (i.e. determined at run-time),
we can use the [`onBeforeRender()` hook](/onBeforeRender).

```js
// rocket.page.route.js

export const '/rocket/:rocketSlug'
```
```js
// rocket.page.server.js

export { onBeforeRender }

function onBeforeRender(pageContext) {
  const documentProps = (() => {
    const shipName = pageContext.routeParams.rocketSlug
    if (shipName==='starship') {
      return {
        title: 'Starship',
        description: 'Starship: deliver payload to Mars'
      }
    }
    if (shipName==='falcon') {
      return {
        title: 'Falcon 9',
        description: 'Falcon 9: deliver payload to Low Earth Orbit'
      }
    }
  })()

  return {
    pageContext: {
      documentProps
    }
  }
}
```
```js
// _default.page.server.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'

export { render }

async function render(pageContext) {
  // We use `pageContext.documentProps` which pages can dynamically
  // define in their `onBeforeRender()` hook.
  const documentProps = pageContext.documentProps

  // Defaults
  const title = documentProps.title || 'SpaceX'
  const description = documentProps.description || 'We deliver payload to space.'

  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}">
    </head>
    <body>
      <div id="root">
        ${dangerouslySkipEscape(await renderToHtml(pageContext.Page))}
      </div>
    </body>
  </html>`
}
```


## View Component

To define `<head>` tags by some deeply nested view (React/Vue/...) component:
 1. We add `documentProps` to [`passToClient`](/passToClient).
 2. We pass `pageContext.documentProps` to all components, see [Guides > Access `pageContext` anywhere](/pageContext-anywhere).
 3. We modify `pageContext.documentProps` in the deeply nested component.

For example:

```js
// _default.page.server.js
// Environment: Node.js

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import renderToHtml from 'some-ui-framework'

export async function render(pageContext) {
  // We use our UI framework to pass `pageContext.documentProps` to all components
  // of our component tree. (E.g. React Context or Vue's `app.config.globalProperties`.)
  const pageHtml = await renderToHtml(
    <ContextProvider documentProps={pageContext.documentProps} >
      <Page />
    </ContextProvider>
  )

  // What happens here is:
  // 1. Our UI framework passed `documentProps` to all our components
  // 2. One of our (deeply nested) component modified `documentProps`
  // 3. We now render `documentProps` to HTML meta tags
  return escapeInject`<html>
    <head>
      <title>${pageContext.documentProps.title}</title>
      <meta name="description" content="${pageContext.documentProps.description}">
    </head>
    <body>
      <div id="app">
        ${dangerouslySkipEscape(pageHtml)}
      </div>
    </body>
  </html>`
}
```

```js
// Somewhere in a component deep inside our component tree

// Thanks to our previous steps, `documentProps` is available here.
documentProps.title = 'I was set by some deep component.'
documentProps.description = 'Me too.'
```


## Client Routing

If we use Client Routing, we need to make sure to update `document.title` upon page change:

```js
// _default.page.server.js

// We make `pageContext.documentProps` available in the browser.
export const passToClient = ['documentProps', 'pageProps']
```
```js
// _default.page.client.js

import { useClientRouter } from 'vite-plugin-ssr/client/router'

useClientRouter({
  render(pageContext) {
    if( ! pageContext.isHydration ) {
      document.title = pageContext.documentProps.title
    }
    // ...
  }
})
```


## Libraries

We can also use libraries such as [@vueuse/head](https://github.com/vueuse/head) or [react-helmet](https://github.com/nfl/react-helmet).

We should use such library only if we have a rationale:
the aforementioned solutions are simpler and work for most use cases.

Head libraries already sanitize the HTML `<head>`, this means we can skip `escapeInject` and wrap the overall result with `dangerouslySkipEscape()`.

```js
// _default.page.server.js
// Environment: Node.js

import { dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'

export async function render(pageContext) {
  return dangerouslySkipEscape(await renderToHtml(pageContext.Page))
}
```


## Markdown

For pages defined with markdown, see [Integration > Markdown > `<head>` (`pageContext.pageExports`)](/markdown#head-pagecontext-pageexports).

